package org.yinxianren.java.test.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Java提供另外的机制用来同步代码块。它比synchronized关键字更加强大、灵活。
 *  它是基于Lock接口和实现它的类（如ReentrantLock）。这种机制有如下优势：
 *
 * 它允许以一种更灵活的方式来构建synchronized块。使用synchronized关键字，
 * 你必须以结构化方式得到释放synchronized代码块的控制权。Lock接口允许你获得更复杂的结构来实现你的临界区。
 *
 * Lock 接口比synchronized关键字提供更多额外的功能。
 *
 * 新功能之一是实现的tryLock()方法。这种方法试图获取锁的控制权并且如果它不能获取该锁，
 * 是因为其他线程在使用这个锁，它将返回这个锁。
 *
 * 使用synchronized关键字，当线程A试图执行synchronized代码块，如果线程B正在执行它，
 * 那么线程A将阻塞直到线程B执行完synchronized代码块。
 *
 * 使用锁，你可以执行tryLock()方法，
 * 这个方法返回一个 Boolean值表示，是否有其他线程正在运行这个锁所保护的代码。
 *
 * 当有多个读者和一个写者时，Lock接口允许读写操作分离。
 *
 * Lock接口比synchronized关键字提供更好的性能。
 *
 * 在这个指南中，你将学习如何通过锁来同步代码块和通过Lock接口及其实现者ReentrantLock类来创建临界区，实现一个程序来模拟打印队列。
 */
public class DemoLock {
    public static void main(String[] args) {
        PrintQueue printQueue=new PrintQueue();

        Thread[] thread = new Thread[10];
        for (int i=0; i<10; i++){
            thread[i]=new Thread(new Job(printQueue),"Thread "+ i);
        }
        for (int i=0; i<10; i++){
            thread[i].start();
        }
    }
}

/**
 * 在 printJob()中，PrintQueue类是这个示例的关键所在。
 * 当我们通过锁来实现一个临界区并且保证只有一个执行线程能运行一个代码块，我们必须创建一个ReentrantLock对象。
 * 在临界区的起始部分，我们必须通过使用lock()方法来获得锁的控制权。当一个线程A调用这个方法时，
 * 如果 没有其他线程持有这个锁的控制权，那么这个方法就会给线程A分配这个锁的控制权并且立即返回允许线程A执行这个临界区。
 * 否则，如果其他线程B正在执行由这 个锁控制的临界区，lock()方法将会使线程A睡眠直到线程B完成这个临界区的执行。
 *
 * 在临界区的尾部，我们必须使用unlock()方法来释放锁的控制权，允许其他线程运行这个临界区。
 * 如果你在临界区的尾部没有调用unlock()方法，那么其他正在等待该代码块的线程将会永远等待，造成 死锁情况。
 * 如果你在临界区使用try-catch代码块，别忘了在finally部分的内部包含unlock()方法的代码。
 *
 * Lock 接口（和ReentrantLock类）包含其他方法来获取锁的控制权，那就是tryLock()方法。这个方法与lock()方法的最大区别是，
 * 如果一 个线程调用这个方法不能获取Lock接口的控制权时，将会立即返回并且不会使这个线程进入睡眠。
 * 这个方法返回一个boolean值，true表示这个线程 获取了锁的控制权，false则表示没有。
 *
 * 注释：考虑到这个方法的结果，并采取相应的措施，这是程序员的责任。如果这个方法返回false值，
 * 预计你的程序不会执行这个临界区。如果是这样，你可能会在你的应用程序中得到错误的结果。
 *
 * ReentrantLock类也允许递归调用（锁的可重入性，译者注），当一个线程有锁的控制权并且使用递归调用，它延续了锁的控制权，
 * 所以调用lock()方法将会立即返回并且继续递归调用的执行。此外，我们也可以调用其他方法。
 *
 * 你必须要非常小心使用锁来避免死锁，这种情况发生在，当两个或两个以上的线程被阻塞等待将永远不会解开的锁。
 * 比如，
 * 线程A锁定Lock(X)
 * 而线程B锁定 Lock(Y)。
 * 如果现在，线程A试图锁住Lock(Y)
 * 而线程B同时也试图锁住Lock(X)，这两个线程将无限期地被阻塞，因为它们等待的锁将不会被解开。
 * 请注意，这个问题的发生是因为这两个线程尝试以相反的顺序获取锁（译者注：锁顺序死锁）。
 * 在附录中，提供了一些很好的并发编程设计的建议，适当的设计并发应用程序，来避免这些死锁问题。
 */
/*
Thread 0: Going to print a document
Thread 6: Going to print a document
Thread 9: Going to print a document
Thread 7: Going to print a document
Thread 8: Going to print a document
Thread 5: Going to print a document
Thread 4: Going to print a document
Thread 3: Going to print a document
Thread 2: Going to print a document
Thread 1: Going to print a document
Thread 0:PrintQueue: Printing a Job during 5 seconds
Thread 0: The document has been printed
Thread 6:PrintQueue: Printing a Job during 4 seconds
Thread 6: The document has been printed
Thread 9:PrintQueue: Printing a Job during 1 seconds
Thread 9: The document has been printed
Thread 7:PrintQueue: Printing a Job during 5 seconds
Thread 7: The document has been printed
Thread 8:PrintQueue: Printing a Job during 3 seconds
Thread 8: The document has been printed
Thread 5:PrintQueue: Printing a Job during 7 seconds
Thread 5: The document has been printed
Thread 4:PrintQueue: Printing a Job during 7 seconds
Thread 4: The document has been printed
Thread 3:PrintQueue: Printing a Job during 4 seconds
Thread 3: The document has been printed
Thread 2:PrintQueue: Printing a Job during 2 seconds
Thread 2: The document has been printed
Thread 1:PrintQueue: Printing a Job during 5 seconds
Thread 1: The document has been printed
 */
class PrintQueue {

    private final Lock queueLock=new ReentrantLock();

    public void printJob(Object document) {
        queueLock.lock();
        try {
            Long duration=(long)(Math.random()*10000);
            System.out.println(Thread.currentThread().getName()+ ":PrintQueue: Printing a Job during "
                    +(duration/1000)+" seconds");
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            queueLock.unlock();
        }
    }

}

class Job implements Runnable {

    private PrintQueue printQueue;
    public Job(PrintQueue printQueue){
        this.printQueue=printQueue;
    }


    @Override
    public void run() {
        System.out.printf("%s: Going to print a document\n", Thread.currentThread().getName());
        printQueue.printJob(new Object());
        System.out.printf("%s: The document has been printed\n",Thread.currentThread().getName());
    }
}

